package ar.edu.utn.frsf.cidisi.upcolbpip.modelgeneration.generator;

import java.util.HashMap;
import java.util.List;
import java.util.Random;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.util.Diagnostician;

import ar.edu.utn.frsf.cidisi.upcolbpip.UPColBPIPModel;
import ar.edu.utn.frsf.cidisi.upcolbpip.UpcolbpipFactory;
import ar.edu.utn.frsf.cidisi.upcolbpip.b2bcollaborations.B2BCollaboration;
import ar.edu.utn.frsf.cidisi.upcolbpip.b2bcollaborations.B2bcollaborationsFactory;
import ar.edu.utn.frsf.cidisi.upcolbpip.collaborativebusinessprocesses.CollaborativeBusinessProcess;
import ar.edu.utn.frsf.cidisi.upcolbpip.collaborativebusinessprocesses.CollaborativebusinessprocessesFactory;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.BusinessMessage;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.Condition;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.ControlFlowSegment;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.InteractionPath;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.InteractionProtocol;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.InteractionProtocolElement;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.InteractionprotocolsFactory;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.Loop;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.LoopType;
import ar.edu.utn.frsf.cidisi.upcolbpip.interactionprotocols.MultipleInstance;

public class CreateModel {

	private UPColBPIPModel upcolbpipModel;
		
	private int maxNodes;
	private int minNodes;
	private int maxNestingLevel;
	private int minNestingLevel;
	private int maxSequence;
	private int minSequence;
	private int maxInteractionPath;
	private int minInteractionPath;
	private int maxHandlers;
	private int minHandlers;
	private int maxInteractionProtocols;
	
	private int constructs;
	
	static public String XOR = "xor";
	static public String AND = "and";
	static public String OR = "or";
	static public String LOOP_WHILE = "loop-while";
	static public String LOOP_UNTIL = "loop-until";
	static public String CANCEL = "cancel";
	static public String EXCEPTION = "exception";
	static public String TERMINATION = "termination";
	static public String MULTIPLE_INSTANCES = "multiple instances";
	static public String BUSINESS_MESSAGE = "business message";
	static public String INTERACTION_PATH = "interaction path";
	
	private int xorId = 0;
	private int andId = 0;
	private int orId = 0;
	private int whileId = 0;
	private int untilId = 0;
	private int cancelId = 0;
	private int excId = 0;
	private int miId = 0;
	private int termId = 0;
	private int bmId = 0;
	private int iPathId = 0;
//	private int iProtId = 0;

	
	private HashMap<Integer,String> elementProbability = new HashMap<Integer,String>();
	
	private InteractionProtocol currentIProt;
	
	/**
	 * Stores the number of elements of a currentIProt.- 
	 */
	private int nodesCount = 0;
	
	/**
	 * A map where the key is an interaction protocol and the value is the number of nodes of such interaction protocol.- 
	 */
	private HashMap<String, Integer> iProtNodesCountMap = new HashMap<String, Integer>();
	
	public CreateModel() {
		
		upcolbpipModel = UpcolbpipFactory.eINSTANCE.createUPColBPIPModel();
		B2BCollaboration b2bCollaboration = B2bcollaborationsFactory.eINSTANCE.createB2BCollaboration();

		upcolbpipModel.setB2bCollaboration(b2bCollaboration);

		CollaborativeBusinessProcess collaborativeBusinessProcess =  CollaborativebusinessprocessesFactory.eINSTANCE.createCollaborativeBusinessProcess();
		b2bCollaboration.getProcesses().add( collaborativeBusinessProcess );

	}
	
	public CreateModel(UPColBPIPModel upcolbpipModel, Experiment experiment) {
		
		this.upcolbpipModel = upcolbpipModel;
		
		minSequence=experiment.getMinSequence(); maxSequence=experiment.getMaxSequence(); 
		minInteractionPath=experiment.getMinInteractionPath(); maxInteractionPath=experiment.getMaxInteractionPath();
		minHandlers=experiment.getMinHandlers(); maxHandlers=experiment.getMaxHandlers();
		minNodes = experiment.getMinNodes(); maxNodes = experiment.getMaxNodes();
		minNestingLevel = experiment.getMinNestingLevel(); maxNestingLevel=experiment.getMaxNestingLevel();
		
		elementProbability = experiment.getElementProbability();
		
		maxInteractionProtocols = experiment.getMaxInteractionProtocols();

	}

	
	public UPColBPIPModel getModelUpcolbpip() {
		return upcolbpipModel;
	}
	
	
	public void createInteractionProtocols(){
		int iProtId = upcolbpipModel.getB2bCollaboration().getProcesses().get(0).getInteractionProtocols().size() + 1;
//        System.out.print("\n");
//		System.out.println("PNC Id"+"\t"+"Nodes Count"+"\t"+"Generation Time [ms]");
		for (int i=0;i<maxInteractionProtocols;i++){
			long startTime = System.nanoTime();
			nodesCount = 0;

			xorId = 0;
			andId = 0;
			orId = 0;
			whileId = 0;
			untilId = 0;
			cancelId = 0;
			excId = 0;
			termId = 0;
			miId = 0;
			bmId = 0;
			iPathId = 0;

			createInteractionProtocol(i+iProtId);
			
			Diagnostic diagnostic = null;
			
			diagnostic = Diagnostician.INSTANCE.validate(currentIProt);
		
//			if (nodesCount>=minNodes && nodesCount<=maxNodes ){
			if (nodesCount>=minNodes && nodesCount<=maxNodes && diagnostic.getSeverity() != Diagnostic.OK){
		        System.out.print(currentIProt.getName()+"\t");
		        System.out.print(iProtNodesCountMap.get(currentIProt.getName())+"\t");
		        System.out.print(iPathId+"\t");
		        System.out.print(andId+"\t");
		        System.out.print(xorId+"\t");
		        System.out.print(orId+"\t");
		        System.out.print(whileId+"\t");
		        System.out.print(untilId+"\t");
		        System.out.print(excId+"\t");
		        System.out.print(cancelId+"\t");
		        System.out.print(miId+"\t");
		        System.out.print(termId+"\t");
		        System.out.print(bmId+"\t");
		        System.out.println((System.nanoTime()-startTime)*1.0/1000/1000);
			} else {
				upcolbpipModel.getB2bCollaboration().getProcesses().get(0).getInteractionProtocols().remove(currentIProt);
//				iProtNodesCountMap.remove(currentIProt.getName());
				i--;
			}

		}
	}
	
	
	private void createInteractionProtocol(int id) {
		
		currentIProt = InteractionprotocolsFactory.eINSTANCE.createInteractionProtocol();
		currentIProt.setName("ip"+id);
		upcolbpipModel.getB2bCollaboration().getProcesses().get(0).getInteractionProtocols().add(currentIProt);

//		Random rNodes = new Random(System.nanoTime());
//		int nodes = minNodes + rNodes.nextInt(maxNodes-minNodes);
		
		Random rSeq = new Random(System.nanoTime());
		int seqs = minSequence + rSeq.nextInt(maxSequence-minSequence)+1;

		Random rLevel = new Random(System.nanoTime());
		int level = minNestingLevel + rLevel.nextInt(maxNestingLevel-minNestingLevel);

		int s=0;
		InteractionProtocolElement oldElement=null;
		while (s<seqs) {

			Random rNode = new Random(System.nanoTime());
			String elementName = elementProbability.get(rNode.nextInt(elementProbability.keySet().size()) + 1);
			
			InteractionProtocolElement newElement = null;

//			if (currentIProt.getIpElement().size()==0)
//				newElement = createElement(BUSINESS_MESSAGE);
//			else
				newElement = createElement(elementName);

			currentIProt.getIpElement().add(newElement);

			setOrder(oldElement, newElement);

			oldElement = newElement;
			
			if (newElement instanceof ControlFlowSegment){
				List<InteractionPath> iPaths = ((ControlFlowSegment)newElement).getInteractionPath();
				for (int i=0;i<iPaths.size();i++){
					createInteractionPath(iPaths.get(i),1,level);
				}
			}
			
			s++;
		}
		
		iProtNodesCountMap.put(currentIProt.getName(), nodesCount);
		
	}
	

	public InteractionPath createInteractionPath(InteractionPath iPath, int currentLevel, int maxLevel) {
		
		Random rSeq = new Random(System.nanoTime());
		int seqs = minSequence + rSeq.nextInt(maxSequence-minSequence);

		int s=0;
		InteractionProtocolElement oldElement=null;
		while (s<seqs && minNestingLevel<maxLevel) {

			String elementName = "";
//			do {
				Random rNode = new Random(System.nanoTime());
				elementName = elementProbability.get(rNode.nextInt(elementProbability.keySet().size()) + 1);
//			} while ((elementName.equals(TERMINATION) && 
//						(iPath.eContainer() instanceof And || iPath.eContainer() instanceof Or || 
//								iPath.eContainer() instanceof MultipleInstance || iPath.eContainer() instanceof Loop))
//					|| ((elementName.equals(CANCEL)) && 
//						(iPath.eContainer() instanceof And || iPath.eContainer() instanceof Or || 
//								iPath.eContainer() instanceof MultipleInstance || iPath.eContainer() instanceof Loop))
//					|| (iPath.getOrderedIpElement().get(iPath.getOrderedIpElement().size()-1) instanceof Termination)
//					);
			
			Random rLevel = new Random(System.nanoTime());
			int level = minNestingLevel + rLevel.nextInt(maxLevel-minNestingLevel);

			InteractionProtocolElement newElement = null;
			
////			if (iPath.getElement().size()==0)
//			if (oldElement==null)
//				newElement = createElement(BUSINESS_MESSAGE);
//			else
			
			if (iPath.getElement().size()==0 && iPath.getOrderedIpElement().size()==1) 
				if (iPath.getInteractionPathBusinessMessages().size()>0)
					oldElement = iPath.getInteractionPathBusinessMessages().get(iPath.getInteractionPathBusinessMessages().size()-1);
			
				newElement = createElement(elementName);
			
			if (newElement instanceof BusinessMessage){
//				((BusinessMessage) newElement).setBmInteractionPath(iPath);
//				iPath.getInteractionPathBusinessMessages().add((BusinessMessage) newElement);
//				currentIProt.getIpElement().add(newElement);
			} else
				iPath.getElement().add(newElement);
			
			setOrder(oldElement, newElement);

			oldElement = newElement;
			
			if (currentLevel < level && newElement instanceof ControlFlowSegment){
				List<InteractionPath> iPaths = ((ControlFlowSegment)newElement).getInteractionPath();
				for (int i=0;i<iPaths.size();i++){
					currentLevel++;
					InteractionPath ip = createInteractionPath(iPaths.get(i),currentLevel,level);

				}
			}
			
			s++;
		}
		
//		if (iPath.getOrderedIpElement().size()==0){
//			InteractionProtocolElement newElement = createElement(BUSINESS_MESSAGE);
//		
//			((BusinessMessage) newElement).setBmInteractionPath(iPath);
//			iPath.getInteractionPathBusinessMessages().add((BusinessMessage) newElement);
//			currentIProt.getIpElement().add(newElement);
////			setOrder(oldElement, newElement);
//		}

		return iPath;
		
	}

	
	private void setOrder(InteractionProtocolElement oldElement, InteractionProtocolElement newElement){
		newElement.setPredecessor(oldElement);
		if (oldElement!=null)
			oldElement.setSuccessor(newElement);
		
	}
	
	public void addInteractionProtocol() {

		InteractionProtocol interactionProtocol = InteractionprotocolsFactory.eINSTANCE.createInteractionProtocol();
		
		upcolbpipModel.getB2bCollaboration()
		.getProcesses().get(0)
		.getInteractionProtocols()
		.add( interactionProtocol );

	}
	
	
	public InteractionProtocolElement createElement(String elementName) {
		InteractionProtocolElement  element = null;
		
//		if(elementName.toLowerCase().equals(INTERACTION_PATH)) {
//			
//			return InteractionprotocolsFactory.eINSTANCE.createInteractionPath();
//	
//		}
//		else 
		if(elementName.toLowerCase().equals(AND)) { 
			element = InteractionprotocolsFactory.eINSTANCE.createAnd();
			nodesCount++;
			andId++;
			element.setId(currentIProt.getName()+"and"+andId);
			element.setName(currentIProt.getName()+"and"+andId);
			element = addInteractionPaths(element);
			return element;
			
		}
		else if(elementName.toLowerCase().equals(XOR)) {
			element = InteractionprotocolsFactory.eINSTANCE.createXor();
			nodesCount++;
			xorId++;
			element.setId(currentIProt.getName()+"xor"+xorId);
			element.setName(currentIProt.getName()+"xor"+xorId);
			element = addInteractionPaths(element);
			return element;
			
		}
		else if(elementName.toLowerCase().equals(OR)) {
			element = InteractionprotocolsFactory.eINSTANCE.createOr();
			nodesCount++;
			orId++;
			element.setId(currentIProt.getName()+"or"+orId);
			element.setName(currentIProt.getName()+"or"+orId);
			element = addInteractionPaths(element);
			return element;
			
		}
		else if(elementName.toLowerCase().equals(LOOP_WHILE)) {
			Loop loop = InteractionprotocolsFactory.eINSTANCE.createLoop();
			nodesCount++;
			whileId++;
			loop.setId(currentIProt.getName()+"while"+whileId);
			loop.setName(currentIProt.getName()+"while"+whileId);
			loop.getInteractionPath().add(createInteractionPath());
			loop.setLoopType(LoopType.WHILE);
			return loop;
			
		}
		else if(elementName.toLowerCase().equals(LOOP_UNTIL)) {
			Loop loop = InteractionprotocolsFactory.eINSTANCE.createLoop();
			nodesCount++;
			untilId++;
			loop.setId(currentIProt.getName()+"until"+untilId);
			loop.setName(currentIProt.getName()+"until"+untilId);
			loop.getInteractionPath().add(createInteractionPath());
			loop.setLoopType(LoopType.UNTIL);
			return loop;
			
		}
		else if(elementName.toLowerCase().equals(EXCEPTION)) {
			element = InteractionprotocolsFactory.eINSTANCE.createException();
			nodesCount++;
			excId++;
			element.setId(currentIProt.getName()+"exc"+excId);
			element.setName(currentIProt.getName()+"exc"+excId);
			element = addHandlers(element);
			return element;
			
		}
		else if(elementName.toLowerCase().equals(CANCEL)) {
			element = InteractionprotocolsFactory.eINSTANCE.createCancel();
			nodesCount++;
			cancelId++;
			element.setId(currentIProt.getName()+"cancel"+cancelId);
			element.setName(currentIProt.getName()+"cancel"+cancelId);
			element = addHandlers(element);
			return element;
			
		}
		else if(elementName.toLowerCase().equals(TERMINATION)) {
		
			element = InteractionprotocolsFactory.eINSTANCE.createTermination();
			nodesCount++;
			termId++;
			element.setId(currentIProt.getName()+"term"+termId);
			element.setName(currentIProt.getName()+"term"+termId);
			return element;
		}
		else if(elementName.toLowerCase().equals(MULTIPLE_INSTANCES)) {
			
			MultipleInstance mi = InteractionprotocolsFactory.eINSTANCE.createMultipleInstance();
			nodesCount++;
			miId++;
			mi.setId(currentIProt.getName()+"mi"+miId);
			mi.setName(currentIProt.getName()+"mi"+miId);
			InteractionPath iPath = createInteractionPath();
			
			Random rIP = new Random();
			int ips = minInteractionPath + rIP.nextInt(maxInteractionPath-minInteractionPath);
			
			Condition cond = InteractionprotocolsFactory.eINSTANCE.createCondition();
			cond.setConditionExpression(""+ips);
			
			iPath.setGuard(cond);
			mi.getInteractionPath().add(iPath);
			return mi;
		}
			
		else if(elementName.toLowerCase().equals(BUSINESS_MESSAGE)) {
			
			element = InteractionprotocolsFactory.eINSTANCE.createBusinessMessage();
			nodesCount++;
			bmId++;
			element.setId(currentIProt.getName()+"bm"+bmId);
			element.setName(currentIProt.getName()+"bm"+bmId);
			return element;
		}
		
		return null;
		
	}
	
	private InteractionProtocolElement addInteractionPaths(InteractionProtocolElement element){

		Random rIP = new Random();
		int ips = minInteractionPath + rIP.nextInt(maxInteractionPath-minInteractionPath);
		
		for (int i=0;i<ips;i++){
			((ControlFlowSegment)element).getInteractionPath().add(createInteractionPath());
		}

		return element;
		
	}
	
	private InteractionProtocolElement addHandlers(InteractionProtocolElement element){

		Random rIP = new Random();
		int ips = minHandlers + rIP.nextInt(maxHandlers-minHandlers);
		
		// Adds the scope
		((ControlFlowSegment)element).getInteractionPath().add(createInteractionPath());
		
		// Adds handlers
		for (int i=0;i<ips;i++){
			((ControlFlowSegment)element).getInteractionPath().add(createInteractionPath());
		}

		return element;
		
	}
	
	private InteractionPath createInteractionPath(){
		InteractionPath iPath = InteractionprotocolsFactory.eINSTANCE.createInteractionPath();
		iPathId++;
		iPath.setName(currentIProt.getName()+"ipath"+iPathId);
		
//		List<InteractionProtocolElement> ipElems = iPath.getOrderedIpElement();
//		InteractionProtocolElement lastElem=null; 
//		if (ipElems.size()>0)
//			lastElem = ipElems.get(ipElems.size()-1);

		InteractionProtocolElement bm = createElement(BUSINESS_MESSAGE); 
		((BusinessMessage) bm).setBmInteractionPath(iPath);
		iPath.getInteractionPathBusinessMessages().add((BusinessMessage) bm);
		currentIProt.getIpElement().add(bm);
		
//		 if (lastElem!=null){
//			 bm.setPredecessor(lastElem);
//			 lastElem.setSuccessor(bm);
//		 }
//		 
		return iPath;
	}
	
	public int getNodesCount(InteractionProtocol ip){
		return iProtNodesCountMap.get(ip).intValue();
	}
	
}
